
module Table
# A Table::RTable
# Class that creates a spreadsheet like table api
# instance variables:
#   @rows => array of hashes (cells) [{:cols=>[]}]
#   @formats => {} stores Spreadseet::Format's for cells
#   @alias_format => {} stores pointers for cells that were created as a push or fill invocation and overflowed the initial cell
#   @ansi => {} stores ansi format code for cells
#   @also => {} stores pointers for cells that were created as a push or fill invocation and overflowed the initial cell
#     # note the above hashes have keys like "x:y" where x is a row and y is a column
#   @defualt_align_method => Symbol defualt is :ljust defines how data is aligned in cell given the @cell_len
#   @cell_len => Fixnum defualt is 20 length of strings in cells, strings over @cell_len
#    used by smart methods like push and fill to maintain a uniform look across formats
#    self.fill_xy does not worry about cell lengths
#   @def_space => String default is " " <- space used by align methods to fill cell remainder
class RTable
  include Table
  attr_accessor :rows,:formats,:max_cols,:cell_len,:def_space,:defualt_align_method,:also,:width,:book
  # width => console display row length Fixnum
  # cell_len => used for smartly displaying cross file formats by smart methods, Fixnum
  # max_cols => Fixnum # no methods implement this but handy for storage to use elswhere
  def initialize width=135,cell_len=20,max_cols=16
    @rows = [{:cols=>[]}]
    @book=nil
    @ansi={}
    @formats = {}
    @cell_len = cell_len
    @max_cols = max_cols
    @defualt_align_method = :ljust
    @def_space=" "
    @alias_format = []
    @also={}
    @width=width
  end

  # returns formatting at row x, column y
  # as english readable values in an array
  def format_at x,y
    fmat = []
    ansi = abs_format self,x,y
    ansi.scan(/\e\[[0-9]+m/).each {|num|
      num=num.gsub("m",'').gsub(/\e\[/,'')
      fmat << get_color_num(num.to_i,true)
    }
    fmat
  end

  def inspect
      "#<#{self.class}:#{object_id} rows:#{length[0]} most columns for a row: #{length[1]}"
      variables = (instance_variables - uninspect_variables).collect do |name|
        "%s=%s" % [name, instance_variable_get(name)]
      end.join(' ')
      uninspect = uninspect_variables.collect do |name|
        var = instance_variable_get name
        "%s=%s[%i]" % [name, var.class, var.size]
      end.join(' ')
      sprintf "#<%s:0x%014x %s %s %s>", self.class, object_id," rows:#{length[0]} most columns for a row: #{length[1]}",
                                     variables, uninspect
  end

  def uninspect_variables # :nodoc:
    %w{@rows @formats @ansi @alias_format @also}
  end

  # most columns used, Fixnum
  def col_length
    len = 0
    @rows.each {|row|
      len = row[:cols].length if row[:cols].length > len
    }
    return len-1
  end
  
  # Array [rows,most_columns_used]
  def length
    [@rows.length-1,col_length]
  end

  # Array of [Spreadsheet::Format,ansi_format_string] for row x, column y
  def property x,y
    arr = []
    if @formats.has_key?("#{x}:#{y}")
      arr << @formats["#{x}:#{y}"][2]
    end
    if @ansi.has_key?("#{x}:#{y}")
      arr << @ansi["#{x}:#{y}"]
    end
    arr
  end

  # add a row
  def add_row
    @rows << {:cols=>[]}
    return @rows.length-1
  end

  # amount of cells in row: x 
  def row_col_length x
    @rows[x][:cols].length-1
  end

  # push alias's this
  # append cell to row: row
  # if foo == Fixnum then set value of cell row,foo to str
  # str => value to set, may be any class as str.to_s is called
  # smartly splits str if str.lenght > self.cell_len and appends the required cells
  # returns cell [initial_cell_created,last_created_cell] of the invocation
  # TODO: return cell value in the array
  def fill str,row,foo=nil
    str = str.to_s
    col = @rows[row][:cols].length unless foo
    col = foo if foo
    ocol = col
    @rows[row][:cols][col]=self.send(@defualt_align_method,str,@cell_len,@def_space)
    done = false
    init_y = col
    until str.length <= @cell_len
      if !@also.has_key?("#{row}:#{init_y}")
        @also["#{row}:#{col}"]=[]
      end
      str = str[@rows[row][:cols][col].length..str.length-1]
      col = col + 1
      @rows[row][:cols][col] = self.send(@defualt_align_method,str,@cell_len,@def_space)
      @alias_format << ["#{row}:#{col}",row,init_y]
      @also["#{row}:#{init_y}"] << col
    end
    [ocol,row_col_length(row)]
  end

  # simply set value of cell row,col to str
  # str => the value to set cell to, any class as str.to_s is called
  # returns column number, todo: return string
  def fill_xy str,row,col
    str = str.to_s
    @rows[row][:cols][col]=str
    col
  end

  # set the format of cell row,col to *opts
  # if cell was created with a smart method like fill (push) the cells in self.also["#{row}:#{col}"]
  #   will also be formatted (tho use self.smart_format else mileage may very)
  # *opts => String,String ... of values fore:[color], back:[color], bold, italic, underline, strike
  def color_xy row,col,*opts
    oa=""
    if @ansi.has_key?("#{row}:#{col}")
      oa = @ansi["#{row}:#{col}"]
    end
    if oa == "" and !@formats.has_key?("#{row}:#{col}")
      ans,excel=ansi(nil,*opts)
    else
      ans,excel=ansi(@formats["#{row}:#{col}"][2],*opts)
    end
    @ansi["#{row}:#{col}"]=ans+oa
    @formats["#{row}:#{col}"]=[row,col,excel]
    begin
    @also["#{row}:#{col}"].each {|c|
      @formats["#{row}:#{c}"]=[row,c,excel]
      @ansi["#{row}:#{c}"]=@ansi["#{row}:#{col}"]
    };rescue;end
  end

  # create an excel work book of self named name in module Spreadsheet
  def to_excel name
    do_excel name,self
  end

  # returns value at cell row,col
  # if row or row:col not used returns error string: #['row' or 'col']: [col or row] -> TABLE.no['Row' or 'Col']Error
  # if error it also does p(error)
  def get_xy row,col
    if row > @rows.length-1 or row < 0
      p e = "#row: #{row} -> TABLE.noRowError"
      e
    else
      if col > @rows[row][:cols].length-1 or col < 0
        p e = "#col: #{col} -> TABLE.noColError"
        e
      else
        str = @rows[row][:cols][col]
        if @also.has_key?("#{row}:#{col}")
          @also["#{row}:#{col}"].each {|col|
            str = str + @rows[row][:cols][col]
          }
        end
        str
      end
    end
  end

  # display the table in the console
  # formatting will display
  def show
    @rows.each_with_index {|r,i|
      print ljust(i.to_s,3)
      show_row i
    }
    nil
  end

  # gets the string and ansi format code at cell x,y
  # returns [code_str,string]
  def show_helper x,y
    str = @rows[x][:cols][y]
    code = ""
    if @ansi.has_key?("#{x}:#{y}")
      code = @ansi["#{x}:#{y}"]
    elsif @alias_format.find {|f| f[0]=="#{x}:#{y}"}
          cx=@alias_format.find {|f| f[0]=="#{x}:#{y}"}[1]
          cy=@alias_format.find {|f| f[0]=="#{x}:#{y}"}[2]
          code = @ansi["#{cx}:#{cy}"]
    end
    [code.to_s,str.to_s+"\e[0m"]
  end

  # display a row
  # row => Fixnum
  # range => defualts to :full. if Range dispaly cells in range of row: row
  def show_row row,range=:full
    if range.class != Range
      @rows[row][:cols].each_with_index {|c,i|
       show_cell row,i,nil,true
      }
    else
      @rows[row][:cols][range].each_with_index {|c,i|
       code,str=show_helper row,i
       print code;print str
      }
    end
    print "\n"
  end

  # displays the cell at row x,column y
  # if also == true will display the cells generated by a smart fill or push also
  # if nonewline == true will not print "\n" 
  def show_cell x,y,also=true,nonewline=false
    code,str = show_helper x,y
    print code;print str
    if @also.has_key?("#{x}:#{y}") and also
      @also["#{x}:#{y}"].each {|c|
        code,str = show_helper x,c
        print code;print str
     }
    end
    print "\n" unless nonewline
  end

  # clear all formats for all cells in self
  def clear_all_formats
    @ansi={}
    @formats={}
  end

  # if foo == nil: clears format in row x
  # if foo is a Fixnum: clear format at cell x,foo
  # if foo is a Range: clear format of cells in range at row x
  def clear_format x,foo=nil
    case foo.class
      when Fixnum
        @ansi.delete("#{x}:#{foo}")
        @formats.delete("#{x}:#{foo}")
      when Range
        for i in foo
          @ansi.delete!("#{x}:#{i}")
          @formats.delete("#{x}:#{i}")
        end
    else
      @rows[x][:cols].each_with_index {|v,c|
        @ansi.delete("#{x}:#{c}")
        @formats.delete("#{x}:#{c}")
      }
    end
  end

  # 
  # if foo is a Fixnum: format cell: x,foo to *opts
  # if format is a Range: format cells in foo at row x to *opts
  # if foo is anything else: set the format of row x to *opts
  # if cell was created with a smart method like fill (push) the cells in self.also["#{row}:#{col}"]
  #   will also be formatted
  # *opts => String,String ... of values fore:[color], back:[color], bold, italic, underline, strike
  def smart_format x,foo,*opts
    case foo
      when Fixnum
        color_xy(x,foo,*opts)
      when Range
        for i in foo
          color_xy(x,i,*opts)
        end
    else
      @rows[x][:cols].each_with_index {|v,c|
        color_xy(x,c,*opts)
      }
    end
    return true
  end
  def is?(x,y,sym)
    if format_at(x,y).index(sym)
      return true
    else
      return nil
    end
  end
  alias :get :get_xy
  alias :format :color_xy
  alias :push :fill
  alias :insert_col :fill
  alias :add :add_row
end;end